#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include "udpxbuf.h"
#include "casock.h"
#include "caevent.h"
#include "calog.h"

CUdpXBuf::CUdpXBuf(es_t* pctx, int bufsize, int mode, CGetPutBuf* gpbuf)
{

	logprt("== udpxbuf const. hdr_size=%d\n", sizeof(udpxhdr_t));
	psys = pctx;
	psock = &sock;
	memset(&sock, 0, sizeof(sock));
	mChunkSize = bufsize -sizeof(udpxhdr_t);
	mPacket = (unsigned char*)malloc(bufsize);
	if(mPacket)
		mPacketBufSize = bufsize;
	else
		mPacketBufSize = 0;
	mGpBuf = gpbuf;
	mSync = 0;
	mTrans = 0;
	mSubSeq = mFrameSeq = 0;
	mData = NULL;
}


CUdpXBuf::~CUdpXBuf()
{
	logprt("== udpxbuf destroy....\n");
	close();
	if(mPacket)
		free(mPacket);
}


int CUdpXBuf::connect(char* ip, int port)
{
	int ret;
	if(sock.fd != 0)
		return 0;
	es_sock_open(psys, psock, KSOCK_TYPE_UDP, sockcb, this);
	ret = es_connect(psock, ip, port);
	es_set_sock_event(psock, 0);
	return ret;
}

int CUdpXBuf::listen(int port)
{
	es_sock_open(psys, psock, KSOCK_TYPE_UDP, sockcb, this);
	return es_sock_listen(psock, "0.0.0.0",  port);
}


void CUdpXBuf::close(void)
{
	es_sock_close(psock);
	
}

void CUdpXBuf::set(unsigned char* src, int size, int chunksize)
{
	mDataSize = size;
	mData = src;
	mChunkSize = chunksize;
	curx = 0;
	isDelayedPacket = 0;
}


int CUdpXBuf::build_packet(void)
{
	int data_len;
	udpxhdr_t* phdr = (udpxhdr_t*)mPacket;
	if(mSync==0)
	{
		mSync = phdr->sync = SYNC_START;
		phdr->len = mDataSize;
		data_len = 0;
		
	}
	else if(mSync == SYNC_START || mSync == SYNC_CONT)
	{
		phdr->sync = SYNC_CONT;
		data_len = mDataSize-curx;
		if(data_len > mChunkSize)
			data_len = mChunkSize;
		phdr->len = data_len;		
		memcpy(phdr+1, mData + curx, data_len);
		
	}
	else if(mSync == SYNC_ONE)
	{
		phdr->sync = SYNC_ONE;
		data_len = mDataSize;
		if(data_len <= mChunkSize)
		{
			phdr->len = data_len;
		}	
		else
		{
			logprt("### Error: data size too big..., data=%d, chunk_size=%d\n", data_len, mChunkSize);
			return 0;
		}
		
	}
	else
	{
		logprt("### Error: logical error, line=%d, \n", __LINE__);
		exit(1);
	}
	mPacketLen = data_len + sizeof(udpxhdr_t);	
	phdr->seq = (mFrameSeq<<4) | (mSubSeq);
	return mPacketLen;
}


int CUdpXBuf::tx()
{
	int ret;
	//logprt("== tx.....\n");
	if(curx == mDataSize)
	{
		logprt("### Error: tx(), curx=%d,\n", curx);
		exit(1);
		return 0;
	}	
	udpxhdr_t* phdr = (udpxhdr_t*)mPacket;
	mTrans = 1;
	for(;;)
	{
		if(isDelayedPacket==0)
		{
			ret = build_packet();		
			if(ret<=0)
				return -1;
		}	
		ret = es_write(psock, mPacket, mPacketLen);
		//logprt("== udp write, hdr_len=%d, mPackletLen=%d, ,sync=%0x, ret=%d\n", phdr->len, mPacketLen, phdr->sync, ret);
		if(ret>0)
		{
			if(ret < mPacketLen) // TODO: check,
			{
				logprt("### Error: partial write, ret=%d, len=%d\n", ret, phdr->len);
				exit(1);
			}

			isDelayedPacket = 0;
			if(phdr->sync == SYNC_CONT)
			{
				curx += phdr->len;
			}
			
			mSubSeq++;
			
			if(curx == mDataSize)
			{
				//logprt("== send complete....\n");
				mTrans = 0;
				mSync = 0;
				mFrameSeq++;
				break;
			}
			else if(curx > mDataSize) // TODO: after test, remove
			{
				logprt("### Error: write logical error,,,\n");
				exit(1);
			}
		}
		else if(ret == 0) // TODO: check
		{
			logprt("### Error: unknown write 0\n");
			exit(1);
		}
		else
		{
			logprt("### Warning: write error, ret=%d, err=%d\n", ret, errno);
			//isDelayedPacket = 1;
			//es_set_sock_event(psock, EPOLLOUT);
			mTrans = 0;
			mSync = 0;
			
			break;
		}
	}
	
	return mTrans;
}



int CUdpXBuf::rx(void)
{
	int recv_result;
	int rdcnt;
	udpxhdr_t *phdr = (udpxhdr_t*)mPacket;
	rdcnt = es_read(psock, (char*)mPacket, mChunkSize);
	//logprt("*** read, rdcnt=%d, errno=%d\n", rdcnt, errno);
	
	if(rdcnt < 0)
	{
		//logprt("### Read error....rdcnt=%d, errno=%d \n", rdcnt, errno);
		return -1;
	}
	else if(rdcnt == 0)
	{
		logprt("### Read zero....\n");
		exit(1); // TODO: check rdcnt zero
		return -1;
	}

	//logprt("***  sync=%0x, fseq=%d, pseq=%d, len=%d, mseq=%d\n", phdr->sync, phdr->seq >> 4, phdr->seq & 0x0f, phdr->len, mSubSeq);
	if(mSync == 0)
	{
		if(phdr->sync == SYNC_START)
		{
			start_recv_packet(phdr);
		}
		else if(phdr->sync == SYNC_ONE)
		{
			logprt("== Recv sync one: ...\n");
			start_recv_packet(phdr);
			if(phdr->len == rdcnt-sizeof(udpxhdr_t) )
			{
				memcpy(mData, phdr+1, phdr->len);
				end_recv_packet();
			}
			else
			{
				logprt("### Error: sync one, size check error\n");
				discard_recv_packet();
			}
			return 0;
		}
		else
		{
			return 0;
		}
	}
	else if(mSync == SYNC_START || mSync == SYNC_CONT)
	{
		if(check_packet_seq(phdr)==true)
		{
			mSubSeq = phdr->seq & 0x0f;
			if(phdr->sync == SYNC_CONT)
			{
				int data_len = rdcnt - sizeof(udpxhdr_t);
				memcpy(mData+curx, mPacket+sizeof(udpxhdr_t), data_len);
				curx += data_len;
				if(curx == mDataSize)
				{
					end_recv_packet();
					return 0;
				}
				else if(curx > mDataSize)
				{
					logprt("### Warning: frame size mismatch....,curx=%d, data_size=%d\n", curx, mDataSize);
					discard_recv_packet();
					return 0;
				}
				mSync = phdr->sync;
			}
			else
			{
				logprt("### packet defected... recv sync byte=%0x \n", phdr->sync);
				discard_recv_packet();
				return 0;
			}
		}
		else
		{
			logprt("### packet seq mismatch...  cur_sync=%0x, packet_sync=%0x, next_seq=%d, packet_seq=%d\n", mSync, phdr->sync, mSubSeq, phdr->seq & 0x0f);
			mSubSeq = phdr->seq & 0x0f;
			discard_recv_packet();
			return 0;
		}
	}
	return -1;
}


void CUdpXBuf::start_recv_packet(udpxhdr_t* phdr)
{
	mTrans = 1;
	if(mData==NULL)
	{
		//mData = (unsigned char*)mGpBuf->get_empty();
		mData = OnPrepareBuffer(phdr->len);
	}	
	mSync = phdr->sync;
	mFrameSeq = (phdr->seq >> 4);
	mSubSeq = phdr->seq & 0x0f;
	mDataSize = phdr->len;
	curx = 0;
}


void CUdpXBuf::end_recv_packet(void)
{
	OnRecvFrame(mData);
	mData = NULL;
	mSync = 0;
	curx = 0;
	mDataSize = 0;
	//logprt("=== recv complete.......\n");
}

void CUdpXBuf::discard_recv_packet(void)
{
	mTrans = 0;
	mSync = 0;
	curx = 0;
	mDataSize = 0;
	
}

bool CUdpXBuf::check_packet_seq(udpxhdr_t* phdr)
{
	//logprt("== check seq, mseq=%d, pseq=%d \n", mSubSeq, phdr->seq & 0x0f );

	int packet_seq = ( phdr->seq & 0x0f);
	int next_seq = mSubSeq+1;
	if(next_seq > SUB_SEQ_LIMIT)
		next_seq = 0;
	
	//logprt("==        nseq=%d, pseq=%d \n", next_seq, packet_seq);
	return (next_seq == packet_seq);
}



int CUdpXBuf::SockProc(int events)
{
	int ret;
	if(events & SOCK_EVENT_WRITE)
	{
		logprt("== write event: \n");
		ret = tx();		
		if(!ret)
		{
			logprt("== write comp....\n");
			es_set_sock_event(psock, EPOLLIN);
		}	
	}
	else if(events & SOCK_EVENT_READ)
	{
		int ret;
		for(;;)
		{
			ret = rx();
			if(ret != 0)
				break;
		}	
		return 0;
	}
	else
	{
		logprt("### Error: logical error, event=%0x\n", events);
		exit(1);
	}

	return ret;
}

		


int CUdpXBuf::status(void)
{
	return mTrans;
}

void CUdpXBuf::OnRecvFrame(unsigned char *frame)
{
	mGpBuf->put_fill( frame - sizeof(unsigned int));
}

unsigned char *CUdpXBuf::OnPrepareBuffer(unsigned int size)
{
	unsigned char* pbuf;
	pbuf = (unsigned char*)mGpBuf->get_empty(true);
	*((unsigned int*)pbuf) = size;
	return (pbuf+sizeof(unsigned int));
}

void CUdpXBuf::OnReleaseBuffer(unsigned char* pbuf)
{
	mGpBuf->put_empty(pbuf-sizeof(unsigned int));
}


static int sockcb(sock_t* psock, int event)
{
	CUdpXBuf* px = (CUdpXBuf*)psock->user;
	return px->SockProc(event);
}

